package org.groovymud.scripting.domain;

import grails.spring.BeanBuilder;
import groovy.lang.Binding;

import java.io.IOException;
import java.util.List;

import groovy.lang.GroovyClassLoader;
import groovy.util.GroovyScriptEngine;
import org.apache.log4j.Logger;
import org.groovymud.config.MudConfig;
import org.groovymud.object.MudObject;
import org.groovymud.object.registry.cloneutils.CloneConverter;
import org.groovymud.scripting.utils.BindingDelegate;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.Resource;

import static java.util.Arrays.asList;
import static org.springframework.beans.factory.BeanFactoryUtils.beanNamesForTypeIncludingAncestors;

/**
 * implementation of domain manager that loads beans using 
 * a grails beanbuilder implementation
 * @author corbym
 *
 */
public class BeanBuilderDomainManager extends AbstractSuffixedScriptedDomainManager<MudObject> implements ApplicationContextAware{

	private static final Logger logger = Logger.getLogger(BeanBuilderDomainManager.class);
	private final String preloadedDomains;
    private ApplicationContext applicationContext;

    public BeanBuilderDomainManager(BindingDelegate bindingDelegate, String preloadedDomains, String... domainSuffixe) {
        super(bindingDelegate, domainSuffixe);
        this.preloadedDomains = preloadedDomains;
    }


    public MudObject getObject(String handle) {
		return (MudObject) applicationContext.getBean(handle);
	}

	public List<String> getHandles() {
		return asList(beanNamesForTypeIncludingAncestors(applicationContext, MudObject.class));
	}

	public boolean canLoad(String domain) {
		if (domain == null) {
			throw new IllegalArgumentException("Domain string cannot be null");
		}
		Resource resource = applicationContext.getResource(domain);
		boolean exists = resource.exists();
		if (!exists) {
			try {
				resource = applicationContext.getResources(domain)[0];
			} catch (IOException e) {
				logger.error("Error loading resource", e);
			}
			exists = resource.exists();
		}
		return exists && resource.isReadable() && domain.length() != 0 && matchesSuffixe(domain);
	}

	public void loadDomain(String domain) {
        logger.info("Loading domain " + domain);
        System.out.println("domain = " + domain);
		Binding binding = getBindingDelegate().createGroovyScriptEngineBinding();

        final GroovyClassLoader groovyClassLoader = applicationContext.getBean(GroovyScriptEngine.class).getGroovyClassLoader();
        System.out.println("groovyClassLoader.getURLs() = " + asList(groovyClassLoader.getURLs()));
        BeanBuilder builder = new BeanBuilder(applicationContext, groovyClassLoader);
		builder.setBinding(binding);
		try {
			builder.loadBeans(domain);
		} catch (IOException e) {
			logger.error("could not load domain: " + domain, e);
		} finally {
			ApplicationContext beanContext = builder.createApplicationContext();
			if (logger.isDebugEnabled()) {
				logger.debug("loaded beans from beanbuilder:" + asList(beanContext.getBeanDefinitionNames()));
			}
            System.out.println("beanContext = " + asList(beanContext.getBeanDefinitionNames()));
            applicationContext = beanContext;
            //applicationContext.getBean(CloneConverter.class).setApplicationContext(applicationContext);
		}
	}

	public void removeObject(String handle) {
		GenericApplicationContext ctx = new GenericApplicationContext(applicationContext);
		ctx.removeBeanDefinition(handle);
	}

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public boolean contains(String handle) {
        return applicationContext.containsBean(handle);
    }

    public void preLoadDomains(){
        loadDomain(preloadedDomains);
    }
}
